/////////////////////////////////////////////////////////////////////////////
//
// (c) 2007 BinaryComponents Ltd.  All Rights Reserved.
//
// http://www.binarycomponents.com/
//
/////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
using SuperListEx.Utility.Collections;
using System.Xml;

namespace SuperListEx.Sections
{
  public class ListSection : ScrollableSection, ILayoutController
  {
    #region vars
    private bool _mouseClickHandled = false;
    private Timer _scrollTimer = null;
    private WeakReference _mapOfPositions = null;
    RowIdentificationList<RowIdentifier> _r = new RowIdentificationList<RowIdentifier>();
    private RowIdentificationList<RowIdentifier> _rowInformation
    {
      get
      {
        return _r;
      }
      set
      {
        _r = value;
      }
    }
    private Direction _layoutDirection = Direction.Forward;
    private GroupState _globalGroupState = GroupState.Expanded;
    private PositionedRowIdentifier _lastRowSelected = null;
    private PositionedRowIdentifier _focusedItem = null;
    private Set<RowIdentifier> _groupExpansionState = new Set<RowIdentifier>();
    private int _lineStart = 0;
    private int _countDisplayed = 0;
    private const int _scrollbarMax = int.MaxValue;
    private HeaderSection _headerSection;
    internal bool _enforceLazyLayout = false;
    private List<RowIdentifier> _lastCalculatedRowInformation = null;
    #endregion

    #region properties
    public bool AllowRowDragDrop
    {
      get;
      set;
    }

    public bool AllowMultiSelect
    {
      get;
      set;
    }

    public int GroupIndent
    {
      get;
      set;
    }

    public virtual int ReservedSpaceFromHeaderFirstColumn
    {
      get
      {
        return ListControl.Columns.GroupedItems.Count * GroupIndent;
      }
    }

    internal ItemLists.ItemList ItemList
    {
      get
      {
        return ListControl.Items;
      }
    }

    internal ColumnList Columns
    {
      get
      {
        return ListControl.Columns;
      }
    }

    public bool ShowHeaderSection
    {
      get
      {
        return _headerSection.IsVisible;
      }
      set
      {
        if (_headerSection.IsVisible != value)
        {
          _headerSection.IsVisible = value;
          LazyLayout();
        }
      }
    }

    public int CalculatedRowsCount
    {
      get
      {
        return _rowInformation == null ? 0 : _rowInformation.Count;
      }
    }

    private int LineStart
    {
      get
      {
        return _lineStart;
      }
      set
      {
        if (_lineStart != value)
        {
          _lineStart = value;
          Layout();
        }
      }
    }

    private ListControl ListControl
    {
      get
      {
        return (ListControl)Host;
      }
    }

    private int MaxWidth
    {
      get
      {
        if (_headerSection.Columns.Count == 0 && !_headerSection.IsVisible)
        {
          int max = 0;
          foreach (Section section in Children)
          {
            if (max < section.Rectangle.Width)
            {
              max = section.Rectangle.Width;
            }
          }
          return max;
        }
        else
        {
          return _headerSection.IdealWidth - 1;
        }
      }
    }

    internal List<RowIdentifier> RowInformation
    {
      get
      {
        if (_rowInformation == null)
        {
          CalculateListItems();
        }
        return _rowInformation;
      }
    }
    #endregion

    #region constructors
    public ListSection(ListControl listControl)
      : base(listControl)
    {
      AllowRowDragDrop = false;
      AllowMultiSelect = true;
      GroupIndent = 10;
      _headerSection = Host.SectionFactory.CreateHeaderSection(listControl, listControl.Columns.VisibleItems);
      _headerSection.LayoutController = this;
      listControl.Columns.GroupedItems.DataChanged += GroupedItems_DataChanged;
      Host.FocusedSection = this; // we handle row focus manually from here.
      Children.Add(_headerSection);
      ExcludeFirstChildrenFromVScroll = 1;
      listControl.SelectedItems.DataChanged += SelectedItems_DataChanged;
    }
    #endregion

    #region public functions
    public override void Dispose()
    {
      base.Dispose();
      StopScrollTimer();
    }

    public bool HasFocus(RowIdentifier ri)
    {
      return Host.FocusedSection == this && _focusedItem != null && _focusedItem.RowIdentifier == ri;
    }

    public int GroupIndexFromGroup(Column groupColumn)
    {
      return ListControl.Columns.GroupedItems.IndexOf(groupColumn);
    }

    public SelectedItemsCollection SelectedItems
    {
      get
      {
        return ListControl.SelectedItems;
      }
    }

    public override void KeyDown(KeyEventArgs e)
    {
      if (RowInformation == null)
      {
        return;
      }

      bool shiftBeingPressed = (e.Modifiers & Keys.Shift) == Keys.Shift;
      bool ctrlBeingPressed = (e.Modifiers & Keys.Control) == Keys.Control;

      int? newPos = null;
      switch (e.KeyCode)
      {
        case Keys.Subtract:
        case Keys.Left:
          {
            if (FocusedItem != null)
            {
              PositionedRowIdentifier groupItem;
              int position = FocusedItem.Position;
              if (FocusedItem.RowIdentifier.GroupColumns != null)
              {
                GroupIdentifier gi = FocusedItem.RowIdentifier as GroupIdentifier;
                if (gi != null)
                {
                  if (GetGroupState(gi) == GroupState.Collapsed && position > 0)
                  {
                    --position;
                  }
                }
              }
              groupItem = FindGroupFromRowIdentifier(position);
              if (groupItem != null)
              {
                SetFocusWithSelectionCheck(groupItem);
                SetGroupState(groupItem.RowIdentifier, GroupState.Collapsed, true);
              }
            }
          }
          break;
        case Keys.Add:
        case Keys.Right:
          {
            if (FocusedItem != null)
            {
              PositionedRowIdentifier groupItem = FindGroupFromRowIdentifier(FocusedItem.Position);
              if (groupItem != null)
              {
                SetGroupState(groupItem.RowIdentifier, GroupState.Expanded, false);
              }
            }
          }
          break;
        case Keys.Multiply:
          SetGlobalGroupState(GroupState.Expanded);
          break;
        case Keys.Divide:
          SetGlobalGroupState(GroupState.Collapsed);
          break;
        case Keys.Down:
          newPos = _focusedItem == null ? 0 : _focusedItem.Position + 1;
          break;
        case Keys.Up:
          if (_focusedItem != null)
          {
            newPos = _focusedItem.Position - 1;
          }
          break;
        case Keys.Space:
          if (FocusedItem != null)
          {
            if (shiftBeingPressed)
            {
              SelectedItems.AddInternal(FocusedItem.RowIdentifier);
            }
            else
            {
              SelectedItems.ClearAndAdd(FocusedItem.RowIdentifier);
            }
            return;
          }
          break;
        case Keys.PageDown:
          newPos = _focusedItem == null ? _countDisplayed : _focusedItem.Position + _countDisplayed;
          break;
        case Keys.PageUp:
          if (_focusedItem != null)
          {
            newPos = _focusedItem.Position - _countDisplayed;
          }
          break;
        case Keys.End:
          newPos = RowInformation.Count;
          break;
        case Keys.Home:
          newPos = 0;
          break;
      }
      if (newPos == null)
      {
        return; // nothing to do
      }

      if (newPos.Value >= RowInformation.Count)
      {
        newPos = RowInformation.Count - 1;
      }
      if (newPos.Value < 0)
      {
        newPos = 0;
      }

      if (newPos.Value < RowInformation.Count)
      {
        RowIdentifier ri = RowInformation[newPos.Value];
        PositionedRowIdentifier newFocusedItem = new PositionedRowIdentifier(ri, newPos.Value);
        if (ctrlBeingPressed)
        {
          FocusedItem = newFocusedItem;
        }
        else
        {
          SetFocusWithSelectionCheck(newFocusedItem);
        }
      }
    }

    public override void MouseWheel(MouseEventArgs e)
    {
      base.MouseWheel(e);

      if (VerticalScrollbarVisible)
      {
        int newScrollValue = VScrollbar.Value - e.Delta * System.Windows.Forms.SystemInformation.MouseWheelScrollLines / 120;

        if (newScrollValue < VScrollbar.Minimum)
        {
          newScrollValue = VScrollbar.Minimum;
        }

        if (newScrollValue > VScrollbar.Maximum - VScrollbar.LargeChange)
        {
          newScrollValue = VScrollbar.Maximum - VScrollbar.LargeChange + 1;
        }

        if (newScrollValue >= VScrollbar.Minimum && newScrollValue <= VScrollbar.Maximum)
          VScrollbar.Value = newScrollValue;
        else if (e.Delta < 0 && VScrollbar.Value - 1 >= VScrollbar.Minimum)
          VScrollbar.Value--;
        else if (e.Delta > 0 && VScrollbar.Value + 1 <= VScrollbar.Maximum)
          VScrollbar.Value++;
      }
    }

    public override void MouseClick(MouseEventArgs e)
    {
      base.MouseClick(e);

      if (!_mouseClickHandled && e.Button != MouseButtons.Right)
      {
        _mouseClickHandled = true;
        RowSection rowSectionSelected = GetRowSectionFromPoint(new Point(e.X, e.Y));
        if (rowSectionSelected == null)
        {
          return;
        }

        RowIdentifier ri = rowSectionSelected.RowIdentifier;
        PositionedRowIdentifier si = new PositionedRowIdentifier(ri, rowSectionSelected.Position);
        SetFocusWithSelectionCheck(si);
      }
    }

    public override void MouseDown(MouseEventArgs e)
    {
      RowSection rowSectionSelected = GetRowSectionFromPoint(new Point(e.X, e.Y));
      if (rowSectionSelected == null)
      {
        return;
      }

      RowIdentifier ri = rowSectionSelected.RowIdentifier;
      PositionedRowIdentifier si = new PositionedRowIdentifier(ri, rowSectionSelected.Position);
      if (!SelectedItems.Contains(ri))
      {
        SetFocusWithSelectionCheck(si);
        _mouseClickHandled = true;
      }
      else
      {
        _mouseClickHandled = false;
        FocusedItem = si;
      }
    }

    public override void PaintBackground(GraphicsSettings gs, Rectangle clipRect)
    {
      gs.Graphics.FillRectangle(SystemBrushes.Window, Rectangle);
      base.PaintBackground(gs, clipRect);
    }

    public void SizeColumnsToFit(params Column[] columns)
    {
      using (Graphics grfx = Host.CreateGraphics())
      {
        GraphicsSettings grfxSettings = new GraphicsSettings(grfx);
        int[] columnWidths = new int[Columns.Count];
        int position = 0;

        HeaderColumnSection[] hcsSections = new HeaderColumnSection[columns.Length];
        for (int iColumn = 0; iColumn < columns.Length; iColumn++)
        {
          HeaderColumnSection hcs = Host.SectionFactory.CreateHeaderColumnSection(Host, DisplayMode.Customise, columns[iColumn]);
          hcs.Layout(grfxSettings, new Size(int.MaxValue, int.MaxValue));
          hcsSections[iColumn] = hcs;
        }

        Type[] lastColumnTypes = new Type[columns.Length];
        CellSection[] columnCellSections = new CellSection[columns.Length];

        //
        //	Pick the widest cell to be the width of the column.
        foreach (object rowItem in ItemList.ToList())
        {
          for (int iColumn = 0; iColumn < columns.Length; iColumn++)
          {
            Column column = columns[iColumn];
            CellSection cs = columnCellSections[iColumn];
            object columnItem;

            if (column.ColumnItemAccessor != null)
            {
              columnItem = column.ColumnItemAccessor(rowItem);
            }

            else
            {
              columnItem = column.ColumnItemAccessorWithParameter(rowItem, column.AccessorParameter);
            }

            Type lastColumnType = lastColumnTypes[iColumn] == null ? null : lastColumnTypes[iColumn].GetType();
            if ((lastColumnType == null || columnItem == null) || columnItem.GetType() != lastColumnType)
            {
              cs = columnCellSections[iColumn] = Host.SectionFactory.CreateCellSection(Host, hcsSections[iColumn], rowItem);
              lastColumnTypes[iColumn] = columnItem == null ? typeof(object) : columnItem.GetType();
            }
            cs.Item = rowItem;
            Size size = cs.GetIdealSize(grfxSettings);
            if (columnWidths[iColumn] < size.Width)
            {
              columnWidths[iColumn] = size.Width;
            }
          }
          position++;

        }

        //
        //	Set columns widths...
        for (int iColumn = 0; iColumn < columns.Length; iColumn++)
        {
          columns[iColumn].Width = columnWidths[iColumn] == 0 ? hcsSections[iColumn].Size.Width : columnWidths[iColumn];
        }
      }
    }

    public void SerializeState(System.IO.TextWriter writer)
    {
      Helper.SerializationState ss = new SuperListEx.Helper.SerializationState();
      List<ColumnState> columnStates = new List<ColumnState>();

      foreach (Column c in Columns)
      {
        ColumnState columnState = new ColumnState();

        columnState.Name = c.Name;
        columnState.SortOrder = c.SortOrder;
        columnState.GroupSortOrder = c.GroupSortOrder;
        columnState.VisibleIndex = Columns.VisibleItems.IndexOf(c);
        columnState.GroupedIndex = Columns.GroupedItems.IndexOf(c);
        columnState.Width = c.Width;
        columnStates.Add(columnState);
      }
      ss.ColumnStates = columnStates.ToArray();

      List<GroupInstance> groupStates = new List<GroupInstance>();

      ss.GlobalGroupState = _globalGroupState;

      foreach (GroupIdentifier gi in _groupExpansionState)
      {
        GroupInstance groupInstance = new GroupInstance();

        groupInstance.GroupPath = gi.GroupValues;
        groupInstance.GroupName = gi.LastColumn.Name;
        groupStates.Add(groupInstance);
      }

      ss.GroupStates = groupStates.ToArray();

      using (XmlTextWriter tw = new XmlTextWriter(writer))
      {
        tw.Formatting = Formatting.Indented;

        Helper.SerializationState.Serialize(tw, ss);
      }
    }

    public void DeSerializeState(System.IO.TextReader reader)
    {
      Helper.SerializationState ss;

      using (XmlTextReader tr = new XmlTextReader(reader))
      {
        ss = Helper.SerializationState.Deserialize(tr);
      }

      if (ss == null || (ss.ColumnStates.Length == 0 && ss.GroupStates.Length == 0))
      {
        return;
      }

      Columns.GroupedItems.Clear();
      Columns.VisibleItems.Clear();

      //
      // Add groups
      Array.Sort(ss.ColumnStates,
             delegate(ColumnState x, ColumnState y)
             {
               return x.GroupedIndex - y.GroupedIndex;
             }
          );
      foreach (ColumnState columnState in ss.ColumnStates)
      {
        if (columnState.GroupedIndex >= 0)
        {
          Column column = Columns.FromName(columnState.Name);
          if (column != null)
          {
            Columns.GroupedItems.Add(column);
          }
        }
      }

      //
      // Add visible items and set properties.
      Array.Sort<ColumnState>(ss.ColumnStates, delegate(ColumnState x, ColumnState y)
                {
                  return x.VisibleIndex - y.VisibleIndex;

                });

      foreach (ColumnState columnState in ss.ColumnStates)
      {
        Column column = Columns.FromName(columnState.Name);
        if (column != null)
        {
          if (columnState.VisibleIndex >= 0)
          {
            Columns.VisibleItems.Add(column);
          }

          column.SortOrder = columnState.SortOrder;
          column.GroupSortOrder = columnState.GroupSortOrder;
          column.Width = columnState.Width;
        }
      }

      //
      // Restore group expansion states
      _groupExpansionState.Clear();
      _globalGroupState = ss.GlobalGroupState;
      _groupExpansionState.Clear();
      foreach (GroupInstance gi in ss.GroupStates)
      {
        Column column = Columns.FromName(gi.GroupName);
        if (column != null)
        {
          _groupExpansionState.Add(new GroupIdentifier(gi.GroupPath, column));
        }
      }
      Host.LazyLayout(null);
    }

    public void SelectAll()
    {
      List<RowIdentifier> allItems = new List<RowIdentifier>(ItemList.Count);
      for (int i = 0; i < ItemList.Count; i++)
      {
        RowIdentifier rowIdentifier = new NonGroupRow(ItemList[i]);
        allItems.Add(rowIdentifier);
      }
      SelectedItems.ClearAndAdd(allItems.ToArray());
      Invalidate();
    }

    public override Point GetScrollCoordinates()
    {
      Point pt = base.GetScrollCoordinates();
      pt.Y = 0;
      return pt;
    }

    public override void Layout(GraphicsSettings gs, System.Drawing.Size maximumSize)
    {
      Size = maximumSize;
      CalculateListItems();
      Column[] groupColumns = ListControl.Columns.GroupedItems.ToArray();
      DeleteRange(ExcludeFirstChildrenFromVScroll, Children.Count - ExcludeFirstChildrenFromVScroll);

      base.Layout(gs, maximumSize);

      if (Columns.Count > 0 && _headerSection.IsVisible)
      {
        HorizontalScrollbarVisible = _headerSection.IdealWidth > maximumSize.Width;
      }

      if (ItemList.Count > 0)
      {
        bool stop = false;
        while (!stop)
        {
          Rectangle rowsRectangle = WorkingRectangle;
          int yPos = _layoutDirection == Direction.Forward ? rowsRectangle.Top : rowsRectangle.Bottom;

          _countDisplayed = 0;

          //
          //	Create sections until either we have no more items
          //	or go over our height
          int position = LineStart;
          foreach (RowIdentifier rowInfo in GetRowEnumerator(LineStart, _layoutDirection))
          {
            if (rowInfo.Items.Count == 0) continue;
            object item = rowInfo.Items[0];

            Section newSection;

            int groupIndex = rowInfo.GroupColumns.Count - 1;

            if (groupIndex != -1 && groupIndex < ListControl.Columns.GroupedItems.Count)
            {
              newSection = Host.SectionFactory.CreateGroupSection(ListControl, rowInfo, _headerSection, position, GroupIndent * groupIndex);
              Children.Add(newSection);
              newSection.Layout(gs, new Size(Size.Width, Size.Height));
            }
            else
            {
              newSection = Host.SectionFactory.CreateRowSection(ListControl, rowInfo, _headerSection, position);
              Children.Add(newSection);
              newSection.Layout(gs, new Size(Size.Width, Size.Height));
            }

            switch (_layoutDirection)
            {
              case Direction.Forward:
                newSection.Location = new Point(Location.X, yPos);
                yPos = newSection.Rectangle.Bottom;
                stop = yPos >= rowsRectangle.Bottom;
                position++;
                break;
              case Direction.Backward:
                newSection.Location = new Point(Location.X, yPos - newSection.Size.Height);
                yPos = newSection.Rectangle.Top;
                stop = yPos <= rowsRectangle.Top;
                position--;
                break;
            }
            if (stop)
            {
              break;
            }

            _countDisplayed++;
          }
          if (!stop) // must have run out of things to fill
          {
            switch (_layoutDirection)
            {
              case Direction.Forward:
                if (LineStart > 0)
                {
                  DeleteRange(ExcludeFirstChildrenFromVScroll, Children.Count - ExcludeFirstChildrenFromVScroll);
                  _layoutDirection = Direction.Backward;
                  _lineStart = _rowInformation.Count - 1;
                }
                else
                {
                  stop = true;
                }
                break;
              case Direction.Backward:
                DeleteRange(ExcludeFirstChildrenFromVScroll, Children.Count - ExcludeFirstChildrenFromVScroll);
                _layoutDirection = Direction.Forward;
                _lineStart = 0;
                break;
            }
          }
          else
          {
            break; // all done
          }
        }
      }

      if (VScrollbar != null)
      {
        VScrollbar.LargeChange = _countDisplayed;
      }

      VerticalScrollbarVisible = _countDisplayed < RowInformation.Count;
      if (Columns.Count == 0 && !_headerSection.IsVisible)
      {
        HorizontalScrollbarVisible = MaxWidth > maximumSize.Width;
      }

      UpdateScrollInfo();
    }
    #endregion

    #region private functions
    private void HandleSyncing(RowIdentifier ri, int newPosition, PositionedRowIdentifier[] trackableItems, List<PositionedRowIdentifier> newSelection)
    {
      foreach (PositionedRowIdentifier si in trackableItems)
      {
        if (si != null)
        {
          if (ri.Equals(si.RowIdentifier))
          {
            si.Position = newPosition;
          }
        }
      }
      if (SelectedItems.Contains(ri))
      {
        newSelection.Add(new PositionedRowIdentifier(ri, newPosition));
      }
    }

    private void GroupedItems_DataChanged(object sender, EventInfo<Column> e)
    {
      _groupExpansionState.Clear();
    }

    private RowSection RowSectionFromRowIdentifier(RowIdentifier ri)
    {
      foreach (Section section in Children)
      {
        RowSection rowSection = section as RowSection;
        if (rowSection != null && rowSection.RowIdentifier == ri)
        {
          return rowSection;
        }
      }
      return null;
    }

    private void SetVScrollValue()
    {
      if (VScrollbar != null)
      {
        switch (_layoutDirection)
        {
          case Direction.Forward:
            VScrollbar.Value = LineStart;
            System.Diagnostics.Debug.Assert(VScrollbar.Value >= 0);
            break;
          case Direction.Backward:
            VScrollbar.Value = LineStart - _countDisplayed + 1;
            System.Diagnostics.Debug.Assert(VScrollbar.Value >= 0);
            break;
        }
      }
    }

    private PositionedRowIdentifier SelectedItemFromRowIdentifer(RowIdentifier ri)
    {
      return new PositionedRowIdentifier(ri, PositionFromRowIdentifier(ri));
    }

    private static bool IsValidNextRow(RowIdentifier ri, RowIdentifier riCompare, ItemLists.ItemList itemList)
    {
      //
      // Only settle on same type rows.
      if ((ri.GroupColumns.Count == 0 ^ riCompare.GroupColumns.Count == 0) == false)
      {
        //
        // Only include items that exist now.
        if (itemList.IndexOf(riCompare.Items[0]) != -1)
        {
          return true;
        }
      }
      return false;
    }

    private static RowIdentifier locateNearestRowThatsStillValid(PositionedRowIdentifier current, ItemLists.ItemList itemList, List<RowIdentifier> oldList)
    {
      //
      // Check forward first
      for (int i = current.Position; i < oldList.Count; i++)
      {
        RowIdentifier ri = oldList[i];
        if (IsValidNextRow(current.RowIdentifier, ri, itemList))
        {
          return ri;
        }
      }

      //
      // Check backwards now
      for (int i = current.Position - 1; i >= 0 && i < oldList.Count; i--)
      {
        RowIdentifier ri = oldList[i];
        if (IsValidNextRow(current.RowIdentifier, ri, itemList))
        {
          return ri;
        }
      }

      return null;
    }

    /// <summary>
    /// Returns the number of items in the list adjusted for the group items.
    /// This method will also create a bit array of types <seealso cref="_positionTypes"/>
    /// </summary>
    /// <returns></returns>
    private int CalculateListItems()
    {
      using (new EnforceLazyLayout(this))
      {
        if (_rowInformation.HasBeenCleared)
        {
          PositionedRowIdentifier[] trackableItems = new PositionedRowIdentifier[3];
          trackableItems[0] = _focusedItem;

          if (_lastCalculatedRowInformation != null)
          {
            if (FocusedItem != null)
            {
              if (LineStart < _lastCalculatedRowInformation.Count)
              {
                trackableItems[1] = new PositionedRowIdentifier(_lastCalculatedRowInformation[LineStart], LineStart);
              }
              RowIdentifier nextValidRow = locateNearestRowThatsStillValid(FocusedItem, ItemList, _lastCalculatedRowInformation);
              if (nextValidRow != null)
              {
                trackableItems[2] = new PositionedRowIdentifier(nextValidRow, 0);
              }
            }
          }

          Column[] groupColumns = ListControl.Columns.GroupedItems.ToArray();
          object lastItem = null;
          _lastCalculatedRowInformation = new List<RowIdentifier>(ItemList.Count);
          int hideRowGroupIndex = -1;
          List<PositionedRowIdentifier> newSelection = new List<PositionedRowIdentifier>();
          _rowInformation.Clear();
          _rowInformation.HasBeenCleared = false;

          GroupIdentifier[] activeGroups = new GroupIdentifier[groupColumns.Length];
          List<object> listItems = ItemList.ToList();
          GroupIdentifier gi = null;
          for (int i = 0; i < ItemList.Count; i++)
          {
            object item = ItemList[i];

            int groupIndex = ItemInNewGroup(groupColumns, item, lastItem);
            if (groupIndex != -1) //item needs a new group
            {
              for (int iGroup = groupIndex; iGroup < groupColumns.Length; iGroup++)
              {
                gi = new GroupIdentifier(i, listItems, Columns, iGroup, item);
                int hideGroupIndex = hideRowGroupIndex;
                if (hideRowGroupIndex == -1 || iGroup <= hideRowGroupIndex)
                {
                  if (GetGroupState(gi) == GroupState.Collapsed)
                  {
                    hideGroupIndex = iGroup;
                    if (groupIndex <= hideRowGroupIndex)
                    {
                      hideRowGroupIndex = -1;
                    }
                  }
                  else
                  {
                    hideRowGroupIndex = hideGroupIndex = -1;
                  }
                }

                if (hideRowGroupIndex == -1 && _rowInformation != null)
                {
                  HandleSyncing(gi, _rowInformation.Count, trackableItems, newSelection);
                  _rowInformation.Add(gi);
                }
                if (activeGroups[iGroup] != null)
                {
                  activeGroups[iGroup].End = i;
                }
                activeGroups[iGroup] = gi;
                hideRowGroupIndex = hideGroupIndex;
              }
            }
            if (hideRowGroupIndex == -1) //item belongs in the last group
            {
              if (gi != null && !gi.Items.Contains(item)) gi.Items.Add(item);
              RowIdentifier ri = new NonGroupRow(item);
              HandleSyncing(ri, _rowInformation.Count, trackableItems, newSelection);
              _rowInformation.Add(ri);
            }
            lastItem = item;
          }

          if (trackableItems[1] != null)
          {
            LineStart = trackableItems[1].Position;
          }
          if (_focusedItem != null && !IsSelectedItemValid(_focusedItem))
          {
            if (trackableItems[2] != null && IsSelectedItemValid(trackableItems[2]))
            {
              PositionedRowIdentifier oldFocusedItem = _focusedItem;
              if (_focusedItem != trackableItems[2])
              {
                _focusedItem = trackableItems[2];
                ListControl.FireFocusChanged(oldFocusedItem == null ? null : oldFocusedItem.RowIdentifier,
                                                                         _focusedItem == null ? null : _focusedItem.RowIdentifier);
              }
              newSelection.Add(_focusedItem);
            }
            else
            {
              int newPosition;
              if (_rowInformation != null && _focusedItem.Position >= _rowInformation.Count)
              {
                newPosition = _rowInformation.Count - 1;
              }
              else
              {
                newPosition = _focusedItem.Position;
              }
              if (newPosition >= 0)
              {
                PositionedRowIdentifier si = new PositionedRowIdentifier(_rowInformation[newPosition], newPosition);
                FocusedItem = si;
                newSelection.Add(_focusedItem);
              }
              else
              {
                FocusedItem = null;
              }
            }
          }
          RowIdentifier[] rowItemsToSelect = new RowIdentifier[newSelection.Count];
          int j = 0;
          foreach (PositionedRowIdentifier pri in newSelection)
          {
            rowItemsToSelect[j++] = pri.RowIdentifier;
          }
          SelectedItems.ClearAndAdd(rowItemsToSelect);
          if (SelectedItems.Count == 0 && FocusedItem == null && VScrollbar != null && VScrollbar.Visible)
          {
            VScrollbar.Value = 0;
          }
        }
        _mapOfPositions = null;
      }
      ListControl.OnCalculatedGroupRows(EventArgs.Empty);
      return _rowInformation == null ? 0 : _rowInformation.Count;
    }

    private bool IsSelectedItemValid(PositionedRowIdentifier item)
    {
      return item != null && (item.Position < _rowInformation.Count && _rowInformation[item.Position] == item.RowIdentifier);
    }

    private PositionedRowIdentifier FindGroupFromRowIdentifier(int position)
    {
      if (position < RowInformation.Count)
      {
        for (int i = position; i >= 0; i--)
        {
          GroupIdentifier gi = RowInformation[i] as GroupIdentifier;
          if (gi != null)
          {
            return new PositionedRowIdentifier(gi, i);
          }
        }
      }
      return null;
    }

    private void ClearRowInformation()
    {
      _rowInformation.Clear();
      _mapOfPositions = null;
    }

    private Dictionary<RowIdentifier, int> GetRowPosMap()
    {
      Dictionary<RowIdentifier, int> mapOfPositions = _mapOfPositions == null ? null : (Dictionary<RowIdentifier, int>)_mapOfPositions.Target;
      if (mapOfPositions == null)
      {
        List<RowIdentifier> rowInformation = _rowInformation == null ? _lastCalculatedRowInformation : _rowInformation;

        if (rowInformation == null)
        {
          CalculateListItems();
        }

        mapOfPositions = new Dictionary<RowIdentifier, int>();
        if (rowInformation != null)
        {
          int i = 0;
          foreach (RowIdentifier ri in rowInformation)
          {
            mapOfPositions[ri] = i++;
          }
        }

        _mapOfPositions = new WeakReference(mapOfPositions);
      }
      return mapOfPositions;
    }

    private int PositionFromRowIdentifier(RowIdentifier ri)
    {
      int position;
      if (GetRowPosMap().TryGetValue(ri, out position))
      {
        return position;
      }

      return -1;
    }

    private void SelectedItems_DataChanged(object sender, SelectedItemsChangedEventArgs e)
    {
      foreach (Section s in Children)
      {
        RowSection rs = s as RowSection;
        if (rs != null)
        {
          if (SelectedItems.Contains(rs.RowIdentifier) != rs.DrawnSelected)
          {
            if (rs.NeedsLayoutOnSelection)
            {
              Layout();
              break;
            }
            else
            {
              rs.Invalidate();
            }
          }
        }
      }
    }

    private void _scrollTimer_Tick(object sender, EventArgs e)
    {
      Host.ProcessLayoutsNow();

      if (IsNearStart(Host.CurrentSectionDraggedOver) && VScrollbar.Value > 0)
      {
        VScrollbar.Value--;
        return;
      }
      if (IsNearEnd(Host.CurrentSectionDraggedOver) && VScrollbar.Value < VScrollbar.Maximum)
      {
        VScrollbar.Value++;
        return;
      }
      if (Host.CurrentSectionDraggedOver == null)
      {
        StopScrollTimer();
      }
    }

    private void Layout()
    {
      if (_enforceLazyLayout)
      {
        Host.LazyLayout(this);
      }
      else
      {
        using (Graphics grfx = Host.CreateGraphics())
        {
          Layout(new GraphicsSettings(grfx), Rectangle.Size);
          Invalidate();
        }
      }
    }

    private RowSection GetRowSectionFromPoint(Point pt)
    {
      Section section = SectionFromPoint(pt);
      while (section != null && !(section is RowSection))
      {
        section = section.Parent;
      }
      return (RowSection)section;
    }

    private void SetFocusWithSelectionCheck(PositionedRowIdentifier si)
    {
      FocusedItem = si;

      bool shiftBeingPressed = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
      bool ctrlBeingPressed = (Control.ModifierKeys & Keys.Control) == Keys.Control;

      if (AllowMultiSelect && _lastRowSelected != null && shiftBeingPressed)
      {
        RowIdentifier anchor = _lastRowSelected.RowIdentifier;
        int from;
        int to;
        if (_lastRowSelected.Position < si.Position)
        {
          from = _lastRowSelected.Position;
          to = si.Position;
        }
        else
        {
          to = _lastRowSelected.Position;
          from = si.Position;
        }
        List<RowIdentifier> selectedItems = new List<RowIdentifier>();
        foreach (RowIdentifier rowInfo in GetRowEnumerator(from, Direction.Forward))
        {
          selectedItems.Add(rowInfo);

          if (from++ == to)
          {
            break;
          }
        }
        if (ctrlBeingPressed)
        {
          SelectedItems.AddRangeInternal(selectedItems.ToArray());
        }
        else
        {
          SelectedItems.ClearAndAdd(selectedItems.ToArray());
        }
      }
      else
      {
        if (ctrlBeingPressed)
        {
          if (SelectedItems.IsSelected(si.RowIdentifier))
          {
            SelectedItems.RemoveInternal(si.RowIdentifier);
          }
          else
          {
            if (AllowMultiSelect)
            {
              SelectedItems.AddInternal(si.RowIdentifier);
            }
            else
            {
              SelectedItems.ClearAndAdd(si.RowIdentifier);
            }
          }
        }
        else
        {
          if (!SelectedItems.IsSelected(si.RowIdentifier) || SelectedItems.Count != 1)
          {
            SelectedItems.Clear();
            SelectedItems.AddInternal(si.RowIdentifier);
          }
        }
        _lastRowSelected = si;
      }
    }

    private IEnumerable<RowIdentifier> GetRowEnumerator(int lineStart, Direction direction)
    {
      if (ItemList.Count > _scrollbarMax)
      {
        throw new NotImplementedException();
      }
      else
      {
        if (RowInformation == null)
        {
          CalculateListItems();
        }
        switch (direction)
        {
          case Direction.Forward:
            for (int i = lineStart; i < RowInformation.Count; i++)
            {
              yield return RowInformation[i];
            }
            break;
          case Direction.Backward:
            for (int i = lineStart; i >= 0; i--)
            {
              if (i < RowInformation.Count)
              {
                yield return RowInformation[i];
              }
            }
            break;
        }
      }
    }

    private static int ItemInNewGroup(Column[] groupColumns, object itemNow, object itemBefore)
    {
      int iColumn = 0;
      foreach (Column column in groupColumns)
      {
        if (column.GroupItemAccessorWithParameter != null)
        {
          if (itemBefore == null || !column.GroupItemAccessorWithParameter(itemNow, column.AccessorParameter).Equals(column.GroupItemAccessorWithParameter(itemBefore, column.AccessorParameter)))
          {
            return iColumn;
          }
        }
        else if (column.GroupItemAccessor != null)
        {
          if (itemBefore == null || !column.GroupItemAccessor(itemNow).Equals(column.GroupItemAccessor(itemBefore)))
          {
            return iColumn;
          }
        }
        iColumn++;
      }
      return -1;
    }

    private bool IsNearStart(Section rowSection)
    {
      int firstCount = 0;
      foreach (Section s in Children)
      {
        if (s is RowSection)
        {
          if (s == rowSection)
          {
            return true;
          }
          if (++firstCount == 2)
          {
            break;
          }
        }
      }
      return false;
    }

    private bool IsNearEnd(Section rowSection)
    {
      if (Children.Count > ExcludeFirstChildrenFromVScroll)
      {
        if (Children[Children.Count - 1] == rowSection)
        {
          return true;
        }
        if (Children.Count - 2 > 0 && Children[Children.Count - 2] == rowSection)
        {
          return true;
        }
      }
      return false;
    }

    public override void DescendentDraggedOver(Section s)
    {
      base.DescendentDraggedOver(s);

      if (IsNearStart(s) && VerticalScrollbarVisible && VScrollbar.Value > 0)
      {
        StartScrollTimer();
      }

      if (IsNearEnd(s) && VerticalScrollbarVisible && VScrollbar.Value < VScrollbar.Maximum)
      {
        StartScrollTimer();
      }
    }

    private void StartScrollTimer()
    {
      if (_scrollTimer == null)
      {
        _scrollTimer = new Timer();
        _scrollTimer.Interval = 200;
        _scrollTimer.Start();
        _scrollTimer.Tick += new EventHandler(_scrollTimer_Tick);
      }
    }

    private void StopScrollTimer()
    {
      if (_scrollTimer != null)
      {
        _scrollTimer.Stop();
        _scrollTimer.Dispose();
        _scrollTimer = null;
      }
    }
    #endregion

    #region internal functions
    internal PositionedRowIdentifier GetPositionedIdentifierFromObject(RowIdentifier identifier)
    {
      int position;
      if (GetRowPosMap().TryGetValue(identifier, out position))
      {
        return new PositionedRowIdentifier(identifier, position);
      }

      return null;
    }

    internal void ListUpdated(bool lazyLayout)
    {
      ClearRowInformation();
      if (Host.IsControlCreated)
      {
        if (lazyLayout)
        {
          Host.LazyLayout(this);
        }
        else
        {
          Layout();
        }
      }
    }

    internal PositionedRowIdentifier FocusedItem
    {
      get
      {
        return Host.FocusedSection == this ? _focusedItem : null;
      }
      set
      {
        if (_focusedItem != value)
        {
          RowSection oldFocusedItem = _focusedItem == null ? null : RowSectionFromRowIdentifier(_focusedItem.RowIdentifier);

          _focusedItem = value;

          if (oldFocusedItem != null)
          {
            oldFocusedItem.LostFocus();
          }

          if (_focusedItem != null)
          {
            RowSection focusedSection = RowSectionFromRowIdentifier(_focusedItem.RowIdentifier);
            Rectangle rowsRect = WorkingRectangle;
            if (focusedSection != null && focusedSection.Rectangle.Top >= rowsRect.Top && focusedSection.Rectangle.Bottom <= rowsRect.Bottom)
            {
              focusedSection.GotFocus();
            }
            else
            {
              //
              // focused item is not in view so we make it visible
              int position = PositionFromRowIdentifier(_focusedItem.RowIdentifier);
              if (position < LineStart)
              {
                _layoutDirection = Direction.Forward;
              }
              else
              {
                _layoutDirection = Direction.Backward;
              }
              LineStart = position;
            }
          }
          ListControl.FireFocusChanged(oldFocusedItem == null ? null : oldFocusedItem.RowIdentifier, _focusedItem == null ? null : _focusedItem.RowIdentifier);
        }
      }
    }

    internal void LazyLayout()
    {
      Host.LazyLayout(null);
    }
    #endregion

    #region protected functions
    protected override void OnVScrollValueChanged(int value)
    {
      int newPosition;
      if (ItemList.Count > _scrollbarMax)
      {
        newPosition = (int)(value / (float)_scrollbarMax * ItemList.Count);
      }
      else
      {
        newPosition = value;
      }
      switch (_layoutDirection)
      {
        case Direction.Forward:
          LineStart = newPosition;
          break;
        case Direction.Backward:
          if (LineStart - _countDisplayed + 1 != newPosition)
          {
            _layoutDirection = Direction.Forward;
            LineStart = newPosition;
          }
          break;
      }
    }

    protected override void SetVScrollInfo()
    {
      VScrollbar.Minimum = 0;
      VScrollbar.SmallChange = 1;
      VScrollbar.LargeChange = _countDisplayed;

      if (ItemList.Count > _scrollbarMax)
      {
        VScrollbar.Maximum = _scrollbarMax - 1;
        ClearRowInformation();
      }
      else
      {
        VScrollbar.Maximum = CalculateListItems() - 1;
      }

      int required;

      switch (_layoutDirection)
      {
        case Direction.Forward:
          required = LineStart;
          break;
        case Direction.Backward:
          required = LineStart - _countDisplayed + 1;
          break;
        default:
          throw new InvalidOperationException();
      }

      required = Math.Min(Math.Max(required, VScrollbar.Minimum), VScrollbar.Maximum);

      try
      {
        VScrollbar.Value = required;
      }
      catch (ArgumentOutOfRangeException)
      {
      }
    }

    protected override void SetHScrollInfo()
    {
      HScrollbar.LargeChange = Rectangle.Width;
      HScrollbar.Minimum = 0;
      HScrollbar.Maximum = MaxWidth;
      HScrollbar.SmallChange = 1;
    }
    #endregion

    #region Group Control

    public GroupState GetGroupState(RowIdentifier ri)
    {
      GroupState opposite = _globalGroupState == GroupState.Expanded ? GroupState.Collapsed : GroupState.Expanded;
      return _groupExpansionState.Contains(ri) ? opposite : _globalGroupState;
    }

    public void SetGroupState(RowIdentifier ri, GroupState groupState, bool layoutNow)
    {
      System.Diagnostics.Debug.Assert(ri.GroupColumns != null);

      if (FocusedItem != null && FocusedItem.RowIdentifier.GroupColumns.Count == 0 && ri.Items.Find(o => o.Equals(FocusedItem.RowIdentifier.Items[0])) != null)
      {
        bool isSelected = SelectedItems.Contains(FocusedItem.RowIdentifier);

        PositionedRowIdentifier pri = GetPositionedIdentifierFromObject(ri);
        if (pri != null)
        {
          if (!SelectedItems.Contains(pri.RowIdentifier))
          {
            SelectedItems.Add(pri.RowIdentifier);
          }
          FocusedItem = pri;
        }
      }

      if (GetGroupState(ri) != groupState)
      {
        if (_globalGroupState == groupState)
        {
          _groupExpansionState.Remove(ri);
        }
        else
        {
          _groupExpansionState.Add(ri);
        }
        ClearRowInformation();
        if (layoutNow)
        {
          Layout();
        }
        else
        {
          Host.LazyLayout(this);
        }
      }
    }

    public void SetGlobalGroupState(GroupState globalGroupState)
    {
      _groupExpansionState.Clear();
      _globalGroupState = globalGroupState;
      ClearRowInformation();
      Host.LazyLayout(this);
    }

    #endregion

    #region ILayoutController Members

    int ILayoutController.ReservedNearSpace
    {
      get
      {
        return ListControl.Columns.GroupedItems.Count * GroupIndent;
      }
    }

    void ILayoutController.HeaderLayedOut()
    {
      if (HorizontalScrollbarVisible)
      {
        SetHScrollInfo();
      }
    }

    public int CurrentHorizontalScrollPosition
    {
      get
      {
        return HorizontalScrollbarVisible ? HScrollbar.Value : 0;
      }
      set
      {
        HorizontalScrollbarVisible = true;
        HScrollbar.Value = value;
      }
    }

    #endregion
  }
}